#!/bin/bash

export TRACE_FILE="$HOME/analyzing.trace"
export SHELL_DEBUG=false
export SHELL_PREF=false
if $SHELL_PREF; then
    echo "# tracer: nop" > $TRACE_FILE
fi

source "$(dirname $(realpath $0))/shell_modules/include.sh"
include "arguments.sh"
include "output.sh"
include "object.sh"
include "yamlv2.sh"

# option -h, --help : 打印本命令的帮助信息
function srepo::option::-h()
{
    srepo::option::--help
}

function srepo::option::--help()
{
    cd $(dirname $(realpath $(which $0)))
    echo "用法: $(cctl b)$0 [选项] [命令 [命令的选项]]$(cctl 0)"

    printf "\n选项:\n"
    local line
    while read line; do
        line="${line//# option }"
        local option="${line// : */}"
        local comment="${line//* : /}"
        printf "    $(cctl g)%-30s$(cctl 0) $(cctl c)%s$(cctl 0)\n" "$option" "$comment"
    done <<< $(cat $0 | egrep "^# option ")

    echo -e "\n命令:"
    local result=$(cat $0 | egrep "^# command")

    echo "$result" | while read str; do
        str="${str:10}"
        local cmd="${str// : */}"
        local comment="${str//* : /}"
        printf "    $(cctl g)%-30s$(cctl 0) $(cctl c)%s$(cctl 0)\n" "$cmd" "$comment"
    done

    exit 0
}

# option -v, --version : 打印本命令的版本信息
function srepo::option::-v()
{
    srepo::option::--version
}

function srepo::option::--version()
{
    cd $(dirname $(realpath $(which $0)))
    logi "version: $(git log --oneline | wc -l)"
}

YAML_REPO_DIR=""
object::define_struct YamlRepo "
    --cargs=name,dir,url
    --string=name
    --string=dir
    --string=url
    --array=actions
    --array=actions_result
"
YAML_REPOS=()

function srepo::_parse_yaml_repos()
{
    Trace::ScopeTrace "srepo::_parse_yaml_repos $*"
    local this="$1"; shift
    logd "parse_yaml_repos: $*"
    if YamlV2::IsExists "$this" "repos" "$@" '$name'; then
        local name="$(YamlV2::GetVal "$this" "repos" "$@" '$name')"
        local url="$1"
        for ((i = 2; i <= $#; i++)); do
            url+="/${!i}"
        done

        YamlRepo robj "$name" "$YAML_REPO_DIR" "$url"
        YAML_REPOS+=("$robj")
        if ! YamlV2::IsExists "$this" repos "$@" '$actions'; then
            return
        fi

        if ! YamlV2::IsArray "$this" repos "$@" '$actions'; then
            logw "$@ 的 \$actions 应该是数组"
            return
        fi

        for ((i = 0; i < $(YamlV2::GetLen "$this" "repos" "$@" '$actions'); i++)); do
            YamlRepo::actions robj push "$(YamlV2::GetVal "$this" "repos" "$@" '$actions' $i)"
        done
        return
    fi

    local keys
    if ! keys="$(YamlV2::GetKeys "$this" repos "$@")"; then
        echo $keys
        return
    fi
    for key in $keys; do
        srepo::_parse_yaml_repos "$this" "$@" $key
    done
}

function srepo::_parse_yaml()
{
    Trace::ScopeTrace "srepo::_parse_yaml $*"
    logd "parse_yaml: $@"

    # opening
    local yaml_filename="$(eval "echo $1")"
    local yaml
    if ! YamlV2 yaml{"$yaml_filename"}; then
        loge "打开yaml文件失败: $yaml_filename"
        return 1
    fi

    # include.before
    if YamlV2::IsExists yaml "include.before"; then
        if ! YamlV2::IsArray yaml "include.before"; then
            loge "yaml的include.before应该是数组"
            return 1
        fi

        local lens="$(YamlV2::GetLen yaml "include.before")"
        for ((i = 0; i < lens; i++)); do
            srepo::_parse_yaml "$(YamlV2::GetVal yaml "include.before" "$i")"
        done
    fi

    # repo_dir
    if ! YamlV2::IsExists yaml "repo_dir"; then
        loge "yaml文件应该有key: repo_dir"
        return 1
    fi
    YAML_REPO_DIR="$(eval echo $(YamlV2::GetVal yaml "repo_dir"))"

    # repos
    if ! YamlV2::IsExists yaml "repos"; then
        loge "yaml文件应该有key: repos $yaml_filename"
        return 1
    fi

    if ! YamlV2::IsObject yaml "repos"; then
        loge "yaml的repos应该是个对象"
        return 1
    fi

    for key in $(YamlV2::GetKeys yaml "repos"); do
        srepo::_parse_yaml_repos yaml "$key"
    done

    # include.after
    if YamlV2::IsExists yaml "include.after"; then
        if ! YamlV2::IsArray yaml "include.after"; then
            loge "yaml的include.after应该是数组"
            return 1
        fi

        local lens="$(YamlV2::GetLen yaml "include.after")"
        for ((i = 0; i < lens; i++)); do
            srepo::_parse_yaml "$(YamlV2::GetVal yaml "include.after" "$i")"
        done
    fi
}

function srepo::operation::ln()
{
    local src="$(realpath $1)"
    local dst="$(eval "echo $2")"
    local dstlink
    local updated=false
    local existed=false
    local notlink=false

    if [ -e "$dst" ]; then
        existed=true
        if [ -L "$dst" ]; then
            dstlink=$(readlink "$dst") || return
            if [ "$dstlink" != "$src" ]; then
                rm $dst || return
                updated=true
            fi
        else
            notlink=true
        fi
    fi

    if ! [ -e "$dst" ]; then
        ln -sf "$src" "$dst" || return
    fi

    if $updated; then
        logi "更新"
    elif $notlink; then
        loge "非链接"
    elif $existed; then
        logw "未变"
    else
        logi "创建"
    fi
} 2>&1

function srepo::operation::sudo_ln_file()
{
    local src="$(realpath $1)"
    local dst="$(eval "echo $2")"
    local dstlink
    local updated=false
    local existed=false

    if [ -e "$dst" ]; then
        existed=true
        if [ -L "$dst" ]; then
            dstlink=$(readlink "$dst") || return
            if [ "$dstlink" != "$src" ]; then
                rm $dst || return
                updated=true
            fi
        else
            sudo rm -f $dst
        fi
    fi

    if ! [ -e "$dst" ]; then
        sudo ln -sf "$src" "$dst" || return
    fi

    if $updated; then
        logi "更新"
    elif $existed; then
        logw "未变"
    else
        logi "创建"
    fi
} 2>&1

function srepo::operation::chmod()
{
    local permission_number="$1"; shift
    local needchmod="false"
    local permission_str=""
    for ((i = 8; i >= 0; i--)); do
        if (( $permission_number & (1 << i) )); then
            if ((i % 3 == 2)); then
                permission_str+="r"
            elif ((i % 3 == 1)); then
                permission_str+="w"
            elif ((i % 3 == 0)); then
                permission_str+="x"
            fi
        else
            permission_str+="-"
        fi
    done

    while read line; do
        if ! [[ "$line" =~ (^.$permission_str) ]]; then
            needchmod="true"
            break
        fi
    done <<< "$(ls -l "$@")"

    if $needchmod; then
        if chmod $permission_number "$@"; then
            logi "成功"
        fi
    else
        logw "未变"
    fi
}

function srepo::operation::taraxf()
{
    local zipfile="$(realpath $1)"; shift
    local dst_dir="$1"; shift
    local changed="false"
    local succeed="false"

    if ! [ -e "$dst_dir" ]; then
        changed="true"
        mkdir -p $dst_dir
        cd $dst_dir
        if tar axf $zipfile; then
            succeed="true"
        else
            rm -rf $dst_dir
        fi
        cd - >/dev/null
    fi

    if ! $changed; then
        logw "未变"
    elif $succeed; then
        logi "成功"
    else
        loge "失败"
    fi
}

# command init : 初始化
function srepo::command::init()
{
    srepo::_parse_yaml $1

    for robj in ${YAML_REPOS[*]}; do
        local dir="$(YamlRepo::dir robj)"
        [ -d "$dir" ] || mkdir -p "$dir"
        cd "$dir"

        local name="$(YamlRepo::name robj)"
        local url="$(YamlRepo::url robj)"
        if ! [ -d "$name" ]; then
            logi "cloning $name from $url"
            if git clone "$url" "$name"; then
                logi "clone $name success"
            else
                loge "clone $name failed"
                continue
            fi
        else
            logi "cloned $name"
        fi

        cd "$dir/$name"
        for ((i = 0; i < $(YamlRepo::actions robj len); i++)); do
            arguments $(YamlRepo::actions robj $i)
            operation="srepo::operation::$(arguments::get)"
            arguments::next
            local param=($(arguments::get_rest))
            arguments::destroy

            YamlRepo::actions_result robj push "$(call $operation ${param[*]})"
        done
    done

    for robj in ${YAML_REPOS[*]}; do
        local name="$(YamlRepo::name robj)"
        for ((i = 0; i < $(YamlRepo::actions robj len); i++)); do
            echo "$name | $(YamlRepo::actions robj $i) | $(YamlRepo::actions_result robj $i)"
        done
    done
}

# command list : 列出仓库
function srepo::command::list()
{
    srepo::_parse_yaml $1

    for robj in ${YAML_REPOS[*]}; do
        logi "$(YamlRepo::url robj):"
        logi "  dir: $(YamlRepo::dir robj)"
        logi "  name: $(YamlRepo::name robj)"
        logi "  actions:"
        for ((i = 0; i < $(YamlRepo::actions robj len); i++)); do
            logi "    - $(YamlRepo::actions robj $i)"
        done
    done
}

# command status : 查看状态
function srepo::command::status()
{
    srepo::_parse_yaml $1

    for robj in ${YAML_REPOS[*]}; do
        local name="$(YamlRepo::name robj)"
        local dir="$(YamlRepo::dir robj)"
        local path="${dir}/${name}"
        if ! pushd ${path} >/dev/null 2>&1; then
            loge "${name} 路径(${path}) 进入失败"
            continue
        fi

        local diffhead="$(git status --short)"
        if [ "${diffhead}" == "" ]; then
            logi "${name} (${path}): 无修改"
        else
            logw "${name} (${path}): 有修改\n$(cctl 0)${diffhead}"
        fi
        popd >/dev/null 2>&1
    done
}

# command sync : 同步
function srepo::command::sync()
{
    srepo::_parse_yaml $1

    for robj in ${YAML_REPOS[*]}; do
        local name="$(YamlRepo::name robj)"
        local dir="$(YamlRepo::dir robj)"
        local path="${dir}/${name}"
        if ! pushd ${path} >/dev/null 2>&1; then
            loge "${name} 路径(${path}) 进入失败"
            continue
        fi

        local diffhead="$(git status --short)"
        if ! [ "${diffhead}" == "" ]; then
            logw "${name} (${path}): 有修改\n$(cctl 0)${diffhead}"
            popd >/dev/null 2>&1
            continue
        fi

        logd "${name} 正在拉取新修改"
        if git pull; then
            logi "拉取成功"
        else
            loge "拉取失败"
        fi
        popd >/dev/null 2>&1
    done
}

function srepo::main()
{
    Trace::ScopeTrace "srepo:main"
    arguments "$@"
    arguments::process_option "help" "break" "srepo::option::"
    arguments::use_skip
    if arguments::is_end; then
        arguments::destroy
        return 0
    fi

    local command="$(arguments::get)"
    local func="srepo::command::${command}"
    if ! declare -f $func 1>/dev/null; then
        loge "无法处理参数: $(arguments::get_rest)"
        return 1
    else
        arguments::next
        arguments::skip_all
        local skipped=($(arguments::get_skipped))
        arguments::destroy
        Trace::StartTrace "$func"
        $func "${skipped[*]}"
        Trace::EndTrace
    fi
}

srepo::main "$@"
